package com.hmkx.app.rest.base;

import com.hmkx.app.util.log.WarnPushUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.util.ResourceUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


/**
 * 请求 日志、性能监控、IP频率控制
 * @author zhouguofei
 */
public class LogInterceptor extends HandlerInterceptorAdapter {

	private static Logger logger = LoggerFactory.getLogger(LogInterceptor.class);

	private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("excute_time");

	//ip访问频率限制  10秒 最大 访问次数
	private static Map<String, List<Long>> accessDatas = new ConcurrentHashMap<>();//存储每个ip的访问记录
	static final long  timeWindowMillis = 10*1000;//10秒
	static volatile int gloabalScanFlag = 0;//扫描次数
	static final int cleanNullSize = 10000;//ip最大空间，超出后清理无访问记录的ip
	static final int putFullQueueSize = 20;//最大存储数 超出清理空间
	static final int permits = 50;//最大存储数 超出清理空间
	static volatile boolean  cleanNulling = false;
	private  static  List<String> maybeFullIds = new ArrayList();

	//ip 白名单
	protected static Set<String> whiteIpSet;
	
	static {
		logger.info("加载访问ip 白名单");
		whiteIpSet = new HashSet<String>();
		String filePath = LogInterceptor.class.getResource("/").getPath()+"whiteip.txt";
		File file = null;
		try {
			file = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "whiteip.txt");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		InputStreamReader read = null;
		try {
			read = new InputStreamReader(new FileInputStream(file),"UTF-8");
			if(file.isFile() && file.exists()){
				BufferedReader bufferedReader = new BufferedReader(read);
				String ip = null;
				while((ip = bufferedReader.readLine()) != null){
					whiteIpSet.add(ip);
			    }
			}else{
				logger.warn("ip白名单不存在");
			}
		} catch (Exception e) {
			logger.error("加载ip白名单异常");
		}finally{
			if(read!=null){
				try {
					read.close();
				} catch (IOException e) {
					logger.error("加载ip白名单异常");
				}
			}
		}
	}


	private static void initExpireTool(){
		//定时清理 ip访问记录
		ScheduledExecutorService executor=Executors.newScheduledThreadPool(2);
		executor.scheduleAtFixedRate(new Runnable() {
			 @Override
			 public void run() {
				 long currentTime = System.currentTimeMillis();

				 List < Long > accessPoints = null;
				 Iterator < String > idsIterator;
				 if (gloabalScanFlag == 5) {//每隔5秒钟 清理所有ip过期访问记录
					 //清理空记录
					 if (cleanNulling = accessDatas.size() > cleanNullSize) {
						 logger.debug("cleanNulling...");
						 Set < String > keys = accessDatas.keySet();
						 for (String key: keys) {
							 List < Long > points = accessDatas.get(key);
							 if (points.isEmpty()) {
								 points = null;
								 accessDatas.remove(key);
							 }
						 }
						 cleanNulling = false;
					 }

					 idsIterator = accessDatas.keySet().iterator();
					 gloabalScanFlag = 0;
				 } else {//每秒钟清理快到阈值的ip
					 idsIterator = maybeFullIds.iterator();
					 gloabalScanFlag++;
				 }

				 while (idsIterator.hasNext()) {
					 accessPoints = accessDatas.get(idsIterator.next());
					 removeExpirePoints(accessPoints, currentTime);
				 }
			 }

		 },1000, 1000, TimeUnit.MILLISECONDS);
	}
	

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		String ip = getIpAddr(request);
		String uri = request.getRequestURI();
		if(!requestFrequencyCheck(ip, uri)){
			WarnPushUtil.sendWarnInfo("ip访问频率报警","ip:"+ip+"==="+"uri:"+uri,0);
			return false;
		}
		long beginTime = System.currentTimeMillis();
		startTimeThreadLocal.set(beginTime);
		return true;
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {
		long endTime = System.currentTimeMillis();
		long beginTime = startTimeThreadLocal.get();
		startTimeThreadLocal.remove();
		//计算请求处理时间
		long consumeTime = endTime - beginTime;
		//请求处理时间 超过1000ms  太慢
		if (consumeTime > 1000) {
			String logStr = String.format("SLOW WARN: %s consume %d millis",request.getRequestURI(), consumeTime);
			WarnPushUtil.sendWarnInfo("慢请求警告","慢请求："+request.getRequestURI()+",耗时："+consumeTime,0);
		}
	}


	/**
	 * 访问频率检查
	 * @param identification 用户标识(ip or sessionId)
	 * @param uri
	 * @return
	 * @copyright
	 */
	private boolean requestFrequencyCheck(String identification, String uri) {
		while (cleanNulling){
			//正在清理空间
		}
		boolean result = false;
		long currentTime = System.currentTimeMillis();
		List < Long > accessPoints = accessDatas.get(identification);
		try {
			if (accessPoints == null) {
				accessPoints = new Vector(permits);
				accessDatas.put(identification, accessPoints);
			}

			int size;
			if ((size = accessPoints.size()) < permits) {
				if (size >= putFullQueueSize) {
					maybeFullIds.add(identification);
				}
				result = true;
			} else {
				int removeNums = removeExpirePoints(accessPoints, currentTime);
				result = removeNums > 0;
			}
			return result;
		} finally {
			accessPoints.add(currentTime);
		}
	}
	/**
	 * 清理过期访问记录
	 * @param ponits
	 * @param currentTimeMillis
	 * @return
	 */
	private static int removeExpirePoints(List<Long> ponits,long currentTimeMillis){
		int removeNums = 0;
		if(ponits == null || ponits.isEmpty()){
			return removeNums;
		}
		Iterator<Long> pointsIterator = ponits.iterator();
		while (pointsIterator.hasNext()) {
			if(pointsIterator.next().compareTo(currentTimeMillis - timeWindowMillis) <= 0){
				pointsIterator.remove();
				removeNums++;
			}else{
				break;
			}
		}

		return removeNums;
	}
	/**
	 * 获取登录用户的IP地址
	 * @param request
	 * @return
	 */
	protected String getIpAddr(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		if (ip.equals("0:0:0:0:0:0:0:1")) {
			ip = "127.0.0.1";
		}
		return ip;
	}
}
